/**
 * the master brain who is responsible for maintaining the essential info
 * of the whole document during the user's editing process
 */

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.text.*;
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;

public class SmartManager
{
	/**
	 * maintain info of the active SDs, that is,
	 * these SDs that are currently being displayed in the editor
	 */
	private ArrayList<SmallDocument> activeSDList = null; 

	/**
	 * filenames of the SDs that before the first active SD and 
	 * have been removed from the editor due to the size limit
	 */
	private ArrayList<String> beforeSDList = null;

	/**
	 * list of objects used to lock the corresponding sub-file,
	 * so if a thread is operating on a sub-file (i.e., upload, rename),
	 * it holds the corresponding lock first,
	 * other threads would not be able to operate on the sub-file 
	 * at the same time, ensure concurrency
	 */
	private ArrayList<Object> lockList = null;

	Stylepad editor;
	private DropboxCommunicator dbComm; 
	private SmallDocument editingSD = null;
	
	private boolean redoDeleteAction = false;

	private javax.swing.Timer m_t;

	/**
	 * how many sub-files should be loaded in the initial phase
	 */
	private static final int BATCH_SIZE = 5;

	/**
	 * conditions used to determine when a sub-file
	 * should be divided or merged
	 */
	private static final int MIN_LINE_NUM = 5;
	private static final int MAX_LINE_NUM = 200;

	/**
	 * at most/least how many SDs can be shown
	 * in the editor UI
	 */
	@SuppressWarnings("unused")
	private static final int MIN_SD_CAN_SHOW = 2;
	private static final int MAX_SD_CAN_SHOW = 5;

	/**
	 * user has performed how many operations before
	 * the corresponding sub-file is locked,
	 * so if lock failed, we know how many steps
	 * we should undo
	 */
	//private int stepsBeforeLock = 0;

	//public static MutableAttributeSet nonLockAttr;
	//public static MutableAttributeSet myLockAttr;
	//public static MutableAttributeSet otherLockAttr;

	public static final int LOCKED_BY_NONE = 0;
	public static final int LOCKED_BY_ME = 1;
	public static final int LOCKED_BY_OTHER = 2;
	
	protected int lastCaretPosition = 0;

	/**
	 * list of files that have been modified by the user,
	 * this list only stores main file names (e.g., para_1.5)
	 * UpdateChecker will ignore these files when checking updated files
	 */
	public static java.util.List<String> ignoredFilelist = Collections.synchronizedList(new ArrayList<String>());

	public SmartManager(Stylepad editor, DropboxCommunicator dbCom) 
	{
		this.editor = editor;
		this.dbComm = dbCom;


		//nonLockAttr = new SimpleAttributeSet();
		//myLockAttr = new SimpleAttributeSet();
		//otherLockAttr = new SimpleAttributeSet();

		//StyleConstants.setBackground(nonLockAttr, Color.WHITE);
		//StyleConstants.setBackground(myLockAttr, new Color(210, 255, 210));
		//StyleConstants.setBackground(otherLockAttr, new Color(215, 215, 215));

		this.editor.getEditor().setHighlighter(new MyHighlighter());
	}

	// Load a list of files and create a list of corresponding SmallDocument objects
	// Author@U097023U 
	public void openAction() 
	{
		//long lStartTime = System.currentTimeMillis();
		// Here.
		UpdateChecker uc = DropboxCommunicator.createUpdateChecker (this);
		uc.recover();
		
		System.out.println("Please wait for loading the initial documents...");
		
		ArrayList<String> filelist = DropboxCommunicator.getDirInfo();

		int numOfFilesToLoad = Math.min(filelist.size(), BATCH_SIZE);

		activeSDList = new ArrayList<SmallDocument>(numOfFilesToLoad);
		lockList = new ArrayList<Object>(numOfFilesToLoad);

		ArrayList<Document> docs = new ArrayList<Document>(numOfFilesToLoad);

		beforeSDList = new ArrayList<String>();
		
		// Load sub-files from Dropbox.
		for (int i = 0; i < numOfFilesToLoad; i++)
		{
			new SingleFileLoader(filelist.get(i), i, docs).start();
		}

		// Waiting until all the files have been loaded.
		while (true)
		{			
			if (docs.size () == numOfFilesToLoad)
				break;
			else
				try 
				{
					Thread.sleep (1000);
				} 
				catch (InterruptedException e) 
				{
					e.printStackTrace ();
				}
		}
		
		
		// Create sub-documents corresponding to the loaded sub-files,
		// and merge the content of the loaded sub-files to display
		// as a whole to the user.
		Document doc = new DefaultStyledDocument();
		for(int i = 0; i < docs.size(); i++){

			/**
			 * create the SmallDocument objects first
			 */
		
			SmallDocument sd = new SmallDocument(filelist.get(i));
			for(int j = 0; j < activeSDList.size(); j++)
				sd.firstParaIndex += activeSDList.get(j).numberOfParas;
			sd.numberOfParas = docs.get(i).getDefaultRootElement().getElementCount();
			
			
			String newFileName = DropboxCommunicator.unlockExpiredLockedFile (filelist.get(i));
			filelist.set (i, newFileName);
			sd.docName = newFileName;
			if(SDAction.isFileLocked (newFileName))
			{
				sd.lockingStatus = LOCKED_BY_OTHER;
			}
			sd.sdIndex = i;

			activeSDList.add(sd);

			lockList.add(new Object());

			
			SDAction.pushString(docs.get(i), doc);
			try {
				doc.remove(doc.getLength()-1, 1);
			} catch (BadLocationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		
		editor.getEditor().setDocument(doc);

		for(int i = 0; i < activeSDList.size(); i++)
			if(activeSDList.get(i).lockingStatus == LOCKED_BY_OTHER)
				setParagraphBackground(activeSDList.get(i), LOCKED_BY_OTHER);

		new AutoFileSaver().start();

		dbComm.startUpdateChecker(this);
		
		/*long lEndTime = System.currentTimeMillis();
		long difference = lEndTime - lStartTime;
		
		try {
		    PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("EfficiencyCheck.txt", true)));
		    out.println("Loading all Sub-Files. Time Elapsed in milliseconds: " + difference);
		    out.close();
		} catch (IOException e) {
		    //oh noes!
		}*/
		
	}

	public SmallDocument findSD(int paraIndex){

		if (activeSDList == null) return null;

		synchronized(activeSDList){

			for (int i = 0; i < activeSDList.size(); i++){

				SmallDocument sd = activeSDList.get(i);

				if (sd.firstParaIndex <= paraIndex && paraIndex < sd.firstParaIndex + sd.numberOfParas)
				{
					return sd;	
				}	
			}
		}

		return null;
	}

	public int getSDIndex(SmallDocument sd){

		if(activeSDList == null)
			return -1;

		synchronized(activeSDList){

			for(int i = 0; i < activeSDList.size(); i++)
				if(sd == activeSDList.get(i))
					return i;
		}

		return -1;
	}

	public SmallDocument getPrevSD (SmallDocument sd)
	{
		if(activeSDList == null)
			return null;

		synchronized(activeSDList){

			int index = getSDIndex(sd);
			if(index > 0 )
				return activeSDList.get(index - 1);
		}

		return null;
	}

	public SmallDocument getNextSD (SmallDocument sd)
	{
		if(activeSDList == null)
			return null;

		synchronized(activeSDList){

			int index = getSDIndex(sd);
			if(index < activeSDList.size() - 1 )
				return activeSDList.get(index + 1);
		}

		return null;

	}

	public String getNextSDNotLoadYet (String dir, SmallDocument sd)
	{

		ArrayList<String> filenameList = null;

		filenameList = DropboxCommunicator.getDirInfo();

		for(int i = 0; i < filenameList.size()-1; i++)
		{
			if(filenameList.get(i).equals(sd.docName))
				return filenameList.get(i+1);
		}

		return "";

	}
	public int getTotalNumOfLinesInSDsNotLoadYet (String dir, int numLoadedSD)
	{

		ArrayList<String> filenameList = null;

		filenameList = DropboxCommunicator.getDirInfo();

		int linesCount = 0;
		for(int i = numLoadedSD; i < filenameList.size(); i++)
		{
			linesCount += SDAction.getLineNoFromFileName(filenameList.get(i));
		}
		return linesCount;

	}

	private void insertSD (SmallDocument sd, int index)
	{
		if(activeSDList == null){
			System.err.println("The activeSDList should not be NULL!!!");
			return;
		}

		synchronized(activeSDList){
			activeSDList.add(index, sd);

			for (int i = index; i < activeSDList.size(); i++)
			{
				activeSDList.get(i).sdIndex = i;
			}

			lockList.add(index, new Object());
		}

	}

	private void deleteSD(SmallDocument sd){

		if(activeSDList == null){
			System.err.println("The activeSDList should not be NULL!!!");
			return;
		}
		synchronized(activeSDList){

			activeSDList.remove(sd.sdIndex);

			for (int j = sd.sdIndex; j < activeSDList.size(); j++)
			{

				activeSDList.get(j).sdIndex = j;
				activeSDList.get(j).firstParaIndex = activeSDList.get(j).firstParaIndex - sd.numberOfParas;
			}

			lockList.remove(sd.sdIndex);
		}	
	}

	private void deleteSD(int index){

		if(activeSDList == null){
			System.err.println("The activeSDList should not be NULL!!!");
			return;
		}
		synchronized(activeSDList){

			activeSDList.remove(index);

			for (int j = index; j < activeSDList.size(); j++)
			{

				activeSDList.get(j).sdIndex = j;

			}

			lockList.remove(index);
		}	
	}

	public void changeCallBack (DocumentEvent e)
	{
		//System.out.println("get in to change call back...");

		if(activeSDList == null)
			return;

		synchronized(editor){
			
			Element root = editor.getEditor().getDocument().getDefaultRootElement();
			int changedIndex = root.getElementIndex(e.getOffset());
			SmallDocument sd = findSD (changedIndex);
			if(sd == null){
				JOptionPane.showMessageDialog(editor.getFrame(),
	                    "Can not find the corresponding sub-file!",
	                    "Insertion Failed",
	                    JOptionPane.ERROR_MESSAGE);
				return;
			}

			//synchronized(sd){
				
				// if a previous editing SD has not be saved and unlocked
				if(editingSD != null && editingSD != sd)
					leaveCurrentSD (editingSD);

				// if the file is not locked, then try to auto-lock for him
				if(!(sd.lockingStatus == LOCKED_BY_ME)){

					// if there is no locking try in progress
					if(sd.stepsBeforeLock == 0){

						 new FileLocker(sd).start();
						 editingSD = sd;
					}

					if(!sd.isModified)
						sd.isModified = true;
					
					sd.stepsBeforeLock++;
				}
				else{	// sd.isLockedByme = true

					if(!sd.isModified)
						sd.isModified = true;
				}
			//}	 
		}	
	}

	public void insertCallBack (DocumentEvent e)
	{		
		
		
		if (redoDeleteAction)
		{
			redoDeleteAction = false;
			return;
		}
		
		if(activeSDList == null)
			return;

		synchronized(editor){
			synchronized(activeSDList){
				
				Element root = editor.getEditor().getDocument().getDefaultRootElement();
				int changedIndex = root.getElementIndex(e.getOffset());
				SmallDocument sd = findSD (changedIndex);
				if(sd == null){
					JOptionPane.showMessageDialog(editor.getFrame(),
							"Can not find the corresponding sub-file!",
							"Insertion Failed",
							JOptionPane.ERROR_MESSAGE);
					return;
				}

				//synchronized(sd){
				// if a previous editing SD has not be saved and unlocked
				if(editingSD != null && editingSD != sd)
					leaveCurrentSD(editingSD);

				// if the file is not locked, then try to auto-lock for him
				if(!(sd.lockingStatus == LOCKED_BY_ME)){

					// if there is no locking try in progress
					if(sd.stepsBeforeLock == 0){
						//Stylepad.setIsLocking(true);
						new FileLocker(sd).start();
						editingSD = sd;
					}

					sd.stepsBeforeLock++;

					if(!sd.isModified)
						sd.isModified = true;

				}
				else{	// sd.isLockedByme = true

					if(!sd.isModified)
						sd.isModified = true;
				}

				if (e.getChange(root) != null)
				{


					int numAddedParas = e.getChange(root).getChildrenAdded().length;
					sd.numberOfParas += numAddedParas;

					for (int i = sd.sdIndex + 1; i < activeSDList.size(); i++)
						activeSDList.get(i).firstParaIndex += numAddedParas;
				}

			}
			//}
		}
	}

	public void deleteCallBack (DocumentEvent e)
	{
		if(activeSDList == null)
			return;

		synchronized(editor)
		{
			synchronized(activeSDList)
			{
				// @Author U097023U.
				
				Element root = editor.getEditor ().getDocument ().getDefaultRootElement ();
				int changedStartIndex = root.getElementIndex (e.getOffset());
				SmallDocument startChangedSd = findSD (changedStartIndex);
				
				int changedEndIndex = root.getElementIndex (e.getOffset () + e.getLength () - 1);
				SmallDocument lastChangedSd = findSD (changedEndIndex);
				
				System.out.println (changedStartIndex);
				System.out.println (changedEndIndex);
				
				if (startChangedSd == null || lastChangedSd == null)
				{
					JOptionPane.showMessageDialog (editor.getFrame (),
							"Can not find the corresponding sub-file, please restart the application to try again.",
							"Deletion Failed",
							JOptionPane.ERROR_MESSAGE);
					return;
				}
				
				System.out.println (startChangedSd);
				System.out.println (lastChangedSd);
				if (startChangedSd != lastChangedSd)
				{
					new Undo (1).start ();
					return;
				}

				//synchronized(sd){

					// if a previous editing SD has not be saved and unlocked
					if(editingSD != null && editingSD != startChangedSd)
						leaveCurrentSD (editingSD);

					// if the file is not locked, then try to auto-lock for him
					if(!(startChangedSd.lockingStatus == LOCKED_BY_ME))
					{
						// if there is no locking try in progress
						if(startChangedSd.stepsBeforeLock == 0){
							//Stylepad.setIsLocking(true);
							new FileLocker(startChangedSd).start();
							editingSD = startChangedSd;
						}

						startChangedSd.stepsBeforeLock++;
						
						if(!startChangedSd.isModified)
							startChangedSd.isModified = true;
						
					}
					else{	// sd.isLockedByme = true

						if(!startChangedSd.isModified)
							startChangedSd.isModified = true;
					}

					if (e.getChange (root) != null)
					{
						int numRemovedParas = e.getChange (root).getChildrenRemoved().length - 
							e.getChange(root).getChildrenAdded().length;

						startChangedSd.numberOfParas -= numRemovedParas;

						if(startChangedSd.numberOfParas > 0){
							for (int i = startChangedSd.sdIndex + 1; i < activeSDList.size(); i++)
								activeSDList.get(i).firstParaIndex -= numRemovedParas;
						}
						else if(startChangedSd.numberOfParas == 0)
						{
							deleteSD(startChangedSd);

							new FileDeleter(startChangedSd).start();

							editingSD = null;
						}
						else
						{
							JOptionPane.showMessageDialog(editor.getFrame(),
									"Invalid operation, cannot delete!",
									"Deletion Failed",
									JOptionPane.ERROR_MESSAGE);
							return;
						}
					}

					//System.out.println(this.toString());
				//}
			}			
		}	
	}


	/**
	 * reserved for the "Lock" button
	 * @param e
	 */
	public void lockCallBack(ActionEvent e){

		Element root = editor.getEditor().getDocument().getDefaultRootElement();
		int index = root.getElementIndex(editor.getEditor().getCaretPosition());
		if(index < 0){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"Lock failed, invalid caret position!",
					"Lock Failed",
					JOptionPane.ERROR_MESSAGE);
			return;
		}
		SmallDocument sdoc = findSD(index);
		if(sdoc == null){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"Lock failed, cannot find the specified file!",
					"Lock Failed",
					JOptionPane.ERROR_MESSAGE);
			return;
		}

		if(sdoc.lockingStatus == LOCKED_BY_ME){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"Lock failed, the file is already locked by you!",
					"Lock Failed",
					JOptionPane.ERROR_MESSAGE);
			return;
		}

		System.out.println(sdoc.toString());
		boolean result = false;
		result = DropboxCommunicator.lockFile(sdoc.docName);
		if(result){

			sdoc.docName = SDAction.renameFromUnlockToLock(sdoc.docName);
			sdoc.lockingStatus = LOCKED_BY_ME;

			setParagraphBackground(sdoc, 1);

			editingSD = sdoc;
		}
		else{
			JOptionPane.showMessageDialog(editor.getFrame(),
					"It has been locked by other user, please try again 3 minutes later.(cannot rename the file)",
					"Lock Failed",
					JOptionPane.ERROR_MESSAGE);
			return;
		}
	}

	/**
	 * reserved for the "Unlock" button
	 * @param e
	 */
	public void unlockCallBack(ActionEvent e){

		Element root = editor.getEditor().getDocument().getDefaultRootElement();
		int index = root.getElementIndex(editor.getEditor().getCaretPosition());
		SmallDocument sdoc = findSD(index);
		if(sdoc == null){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"Unable to unlock the file, please restart the application.(cannot find the file)",
					"Unlock Failed",
					JOptionPane.ERROR_MESSAGE);
			return;
		}
		if(sdoc.lockingStatus == LOCKED_BY_NONE){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"The file is not locked, cannot unlock it!",
					"Unlock Failed",
					JOptionPane.ERROR_MESSAGE);;
					return;
		}
		if(!(sdoc.lockingStatus == LOCKED_BY_ME)){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"The file is locked by other user,please try again later(#cannot unlock it)",
					"Unlock Failed",
					JOptionPane.ERROR_MESSAGE);;
					return;
		}

		boolean result = false;
		result = DropboxCommunicator.unlockFile(sdoc.docName);
		if(result){

			sdoc.docName = SDAction.renameFromLockToUnlock(sdoc.docName);
			sdoc.lockingStatus = LOCKED_BY_NONE;

			setParagraphBackground (sdoc, 0);
			editor.validate();			
		}
		else{
			JOptionPane.showMessageDialog(editor.getFrame(),
					"The file is locked by other user,please try again later(#2cannot rename the file)",
					"Unlock Failed",
					JOptionPane.ERROR_MESSAGE);;
		}
	}

	public void nextCallBack(){

		SmallDocument lastWorkingFile = activeSDList.get(activeSDList.size()-1);
		if(lastWorkingFile == null){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"Cannot get the last working file,please restart the application to try again.",
					"Next Failed",
					JOptionPane.ERROR_MESSAGE);;

					return;
		}
		/*if(editingSD != null && editingSD == activeSDList.get(0) &&
				activeSDList.size() == MAX_SD_CAN_SHOW){

			JOptionPane.showMessageDialog(editor.getFrame(),
					"# of sub-files reaches the max limitation," +
					" and currently you are working on the first sub-file!",
					"Next Failed",
					JOptionPane.ERROR_MESSAGE);;

					return;         
		}*/
		if(Stylepad.getIsLoadingNext()){
			System.out.println("Can only load one next file at a time, please try again later");
			return;
		}
		
		Stylepad.setIsLoadingNext(true);
		
		new NextFileLoader(lastWorkingFile).start();

	}

	public void previousCallBack(){

		SmallDocument firstWorkingFile = activeSDList.get(0);
		if(firstWorkingFile == null){
			JOptionPane.showMessageDialog(editor.getFrame(),
					"Cannot get the last working file, please restart the application to try again!",
					"Next Failed",
					JOptionPane.ERROR_MESSAGE);;

					return;
		}

		if(editingSD != null && editingSD == activeSDList.get(activeSDList.size()-1) &&
				activeSDList.size() == MAX_SD_CAN_SHOW){

			JOptionPane.showMessageDialog(editor.getFrame(),
					"# of sub-files reaches the max limitation," +
					" and currently you are working on the last sub-file!",
					"Next Failed",
					JOptionPane.ERROR_MESSAGE);;

					return;         
		}

		if(Stylepad.getIsLoadingPrev()){
			System.out.println("Can only load one previous file at a time, please try again later");
			return;
		}
		
		Stylepad.setIsLoadingPrev(true);
		
		new PreviousFileLoader(firstWorkingFile).start();
	}


	public void setParagraphBackground (final SmallDocument sd, int type)
	{

		final SmallDocument sdoc = sd;
		final int tp = type;

		
		SwingUtilities.invokeLater(new Runnable(){public void run(){

			int firstChar = editor.getEditor().getDocument().getDefaultRootElement().
			getElement(sdoc.firstParaIndex).getStartOffset();
			int lastChar = editor.getEditor().getDocument().getDefaultRootElement().
			getElement(sdoc.firstParaIndex + sdoc.numberOfParas - 1).getEndOffset();

			//editor.removeUndoableListener();
			//editor.removeMyDocumentListener();

			DefaultHighlightPainter hp = null;

			if(tp == LOCKED_BY_NONE)
			{
				//((StyledDocument)editor.getEditor().getDocument()).setCharacterAttributes
				//(firstChar, lastChar - firstChar, nonLockAttr, false);
				hp = new DefaultHighlightPainter (Color.WHITE);
			}
			else if(tp == LOCKED_BY_ME)
			{
				//((StyledDocument)editor.getEditor().getDocument()).setCharacterAttributes
				//(firstChar, lastChar - firstChar, myLockAttr, false);
				hp = new DefaultHighlightPainter (new Color(215, 215, 215));
			}
			else if (tp == LOCKED_BY_OTHER)
			{
				hp = new DefaultHighlightPainter (new Color(255, 204, 204));
			}

			Highlighter hl = editor.getEditor().getHighlighter();
			if (sd.highlight != null)
				hl.removeHighlight(sd.highlight);
			try 
			{
				sd.highlight = hl.addHighlight(firstChar, lastChar-1, hp);
				sd.hlPainter = hp;
			} 
			catch (BadLocationException e)
			{
				e.printStackTrace();
			}

			//editor.addUndoableListener();
			//editor.addMyDocumentListener();

			editor.validate();
		}});
	}

	// Author@U097023U
	private void removeHighlight (final SmallDocument sd)
	{
		Highlighter hl = editor.getEditor().getHighlighter();
		if (sd.highlight != null)
		{
			hl.removeHighlight(sd.highlight);
			sd.highlight = null;
		}
	}

	// Author@U097023U
	private void recoverHighlight (final SmallDocument sd)
	{
		int firstChar = editor.getEditor().getDocument().getDefaultRootElement().
		getElement(sd.firstParaIndex).getStartOffset();
		int lastChar = editor.getEditor().getDocument().getDefaultRootElement().
		getElement(sd.firstParaIndex + sd.numberOfParas - 1).getEndOffset();

		Highlighter hl = editor.getEditor().getHighlighter();

		try {
			sd.highlight = hl.addHighlight(firstChar, lastChar-1, sd.hlPainter);
		} catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * save a sub-file which has been modified,
	 * it has been checked that the size of this sub-file is within the range,
	 * so just count the new line number, update its filename,
	 * and then save the file back
	 *
	 */
	public class NormalFileSaver extends Thread{

		SmallDocument currentSD;
		Object isSaving;
		
		public NormalFileSaver(SmallDocument sd, Object saving){

			this.currentSD = sd;
			this.isSaving = saving;
		}

		public void run(){

			synchronized(isSaving){
				
				String oldName;
				String newName;
				Document doc;
				int sdIndex;

				synchronized(editor){
					synchronized(currentSD){
						
						oldName = currentSD.docName;
						
						int numLines = SDAction.getSDLines(editor, currentSD);

						if(SDAction.getLineNoFromFileName(oldName) != numLines)
							currentSD.docName = SDAction.updateLineNoInFileName(oldName, numLines);

						newName = currentSD.docName;
						
						doc = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
								currentSD.firstParaIndex, currentSD.firstParaIndex + currentSD.numberOfParas);
						
						sdIndex = getSDIndex(currentSD);
					}
					
				}

				synchronized(lockList.get(sdIndex)){

					if(!oldName.equals(newName))
						DropboxCommunicator.renameFile(oldName, newName);

					DropboxCommunicator.saveSingleFile(newName , doc, 0);
				}

				printActiveSDList("Normal File Saver");
			}			
		}
	}

	public void printActiveSDList(String inFunc){

		System.out.println ("****************************************");
		String returnString = inFunc +"\n";
		for (int i = 0; i < activeSDList.size(); i++)
		{
			returnString += activeSDList.get(i).toString();
		}
		System.out.print (returnString);		
		System.out.println ("*****************************************");

	}

	/**
	 * load a single sub-file, and then add it to the document list
	 * this class is only used in the initial file-open phase
	 * 
	 * @author g0900398
	 *
	 */
	public class SingleFileLoader extends Thread{

		String filename;
		int index;
		ArrayList<Document> docs;

		public SingleFileLoader(String file, int index, ArrayList<Document> docs) {
			this.filename = file;
			this.index = index;
			this.docs = docs;
		}

		public void run(){

			Document loadedDoc = DropboxCommunicator.loadSingleFile(filename);
			if(loadedDoc == null){
				System.err.println("Cannot read file: " + filename);
				return;
			}

			while(true){
				if(docs.size() == index)
					break;
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			docs.add(index, loadedDoc);

		}
	}

	/**
	 * the user has left the current sd (and moved to a new one),
	 * so try to unlock the editingSD 
	 * (if it has been modified since last saving, try to save it first)
	 * 
	 * it is unclear whether the new SD has been successfully locked by the user,
	 * so just set editingSD to NULL
	 */
	public void leaveCurrentSD (SmallDocument sd) {

		synchronized(sd)
		{
			if(sd.isModified){
				new SaveAndUnlock(sd).start ();
				sd.isModified = false;
			}
			else
				new FileUnlocker(sd).start();

			sd.lockingStatus = LOCKED_BY_NONE;	
		}
		editingSD = null;
	}

	/**
	 * try to save the changes and then unlock the sub-file
	 * 
	 * @author g0900398
	 *
	 */
	public class SaveAndUnlock extends Thread{

		SmallDocument sd;

		public SaveAndUnlock(SmallDocument sd){
			this.sd = sd;
		}

		public void run(){

			int sdLines;
			Document doc;

			synchronized(sd){

				synchronized(editor){

					setParagraphBackground (sd, 0);
					sd.lockingStatus = LOCKED_BY_NONE;
					sdLines = SDAction.getSDLines(editor, sd);
					doc = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
							sd.firstParaIndex, sd.firstParaIndex+sd.numberOfParas);
				}

				synchronized(lockList.get(getSDIndex(sd))){

					if(sdLines != SDAction.getLineNoFromFileName(sd.docName)){
						String oldName = sd.docName;
						sd.docName = SDAction.updateLineNoInFileName(oldName, sdLines);
						DropboxCommunicator.renameFile(oldName, sd.docName);
					}

					DropboxCommunicator.saveSingleFile(sd.docName, doc, 0);

					DropboxCommunicator.unlockFile(sd.docName);

					sd.docName = SDAction.renameFromLockToUnlock(sd.docName);
					

				}
			}

			printActiveSDList("Save and Unlock");
		}
	}

	/**
	 * when user types some words in an unlocked sd, 
	 * this class is started to lock that sd for him
	 *
	 */
	class FileLocker extends Thread{

		SmallDocument sdoc;

		public FileLocker(SmallDocument sd){
			this.sdoc = sd;
		}

		public void run(){

			
			synchronized(sdoc){
				boolean result = false;
				synchronized(lockList.get(getSDIndex(sdoc))){
					result = DropboxCommunicator.lockFile(sdoc.docName);
				}
				
				if(result){
	
					sdoc.docName = SDAction.renameFromUnlockToLock(sdoc.docName);
					sdoc.lockingStatus = LOCKED_BY_ME;

					setParagraphBackground(sdoc, 1);

					//editingSD = sdoc;
				
				}
				else{
					JOptionPane.showMessageDialog(editor.getFrame(),
							"The file is locked by other user, please try again 3 mins later.(1#cannot rename the file)",
							"Lock Failed",
							JOptionPane.ERROR_MESSAGE);
					editor.undoNSteps(sdoc.stepsBeforeLock);
	
					if(sdoc.isModified)
						sdoc.isModified = false;
					
					editingSD = null;
				}
	
				sdoc.stepsBeforeLock = 0;
	
				printActiveSDList("FileLocker");
			}
		}
	}

	/**
	 * if user leaves a sd without unlock it first, 
	 * this class is started to unlock that sd
	 *
	 */
	class FileUnlocker extends Thread{
		SmallDocument sd;

		public FileUnlocker(SmallDocument sd){
			this.sd = sd;
		}

		public void run(){

			setParagraphBackground(sd, 0);

			synchronized(lockList.get(getSDIndex(sd))){

				DropboxCommunicator.unlockFile(sd.docName);

				sd.docName = SDAction.renameFromLockToUnlock(sd.docName);
				sd.lockingStatus = LOCKED_BY_NONE;
			}

			printActiveSDList("FileUnlocker");
		}
	}

	/**
	 * user deletes a sub-file
	 * 
	 * @author g0900398
	 *
	 */
	class FileDeleter extends Thread{

		SmallDocument sd;

		public FileDeleter(SmallDocument sd){
			this.sd = sd;
		}

		public void run(){


			DropboxCommunicator.deleteSingleFile(sd.docName);
			
		}

	}

	/**
	 * load the next sub-file, and then notify the updating function to update the UI
	 * 
	 * @author g0900398
	 *
	 */
	class NextFileLoader extends Thread{

		SmallDocument sd;

		public NextFileLoader(SmallDocument sd){
			this.sd = sd;
		}

		@SuppressWarnings("unchecked")
		public void run(){

			Map map = null;

			map = DropboxCommunicator.loadNextFile (sd.docName);
			if (map == null){
				//JOptionPane.showMessageDialog(editor.getFrame(),
				//        "Cannot get the next file from Dropbox!",
				//        "Next Failed",
				//        JOptionPane.ERROR_MESSAGE);;

				Stylepad.setIsLoadingNext (false);
				
				return;
			}
			// insert the doc into the end of the current document
			final ArrayList<Document> docList = new ArrayList<Document>();
			docList.add((Document)map.get("doc"));
			final ArrayList<String> nameList = new ArrayList<String>();
			nameList.add((String)map.get("filename"));

			SwingUtilities.invokeLater(new Runnable(){public void run(){

				updateNotification(docList, nameList, 2);
			}});

			Stylepad.setIsLoadingNext(false);

		}
	}

	/**
	 * load the previous sub-file, if any
	 * 
	 * @author g0900398
	 *
	 */
	class PreviousFileLoader extends Thread{

		SmallDocument sd;

		public PreviousFileLoader(SmallDocument sd){
			this.sd = sd;
		}

		@SuppressWarnings("unchecked")
		public void run(){

			Map map = null;

			map = DropboxCommunicator.loadPreviousFile(sd.docName);
			if(map == null){
				//JOptionPane.showMessageDialog(editor.getFrame(),
				//        "Cannot get the next file from Dropbox!",
				//        "Next Failed",
				//        JOptionPane.ERROR_MESSAGE);;

				Stylepad.setIsLoadingPrev(false);
				
				return;
			}

			final ArrayList<Document> docList = new ArrayList<Document>();
			docList.add((Document)map.get("doc"));
			final ArrayList<String> nameList = new ArrayList<String>();
			nameList.add((String)map.get("filename"));

			SwingUtilities.invokeLater(new Runnable(){public void run(){

				updateNotification(docList, nameList, 3);
			}});

			Stylepad.setIsLoadingPrev(false);

		}
	}

	/**
	 * when user close the editor UI, make sure that the sub-file 
	 * lock by him has been saved and unlocked
	 */
	public void windowCloseCallback(){

		if(editingSD == null)
			return;

		synchronized(editingSD){

			synchronized(lockList.get(getSDIndex(editingSD))){

				if(editingSD.isModified){
					int sdLines = SDAction.getSDLines(editor, editingSD);
					Document doc = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
							editingSD.firstParaIndex, editingSD.firstParaIndex+editingSD.numberOfParas);

					if(sdLines != SDAction.getLineNoFromFileName(editingSD.docName)){
						String oldName = editingSD.docName;
						editingSD.docName = SDAction.updateLineNoInFileName(oldName, sdLines);
						DropboxCommunicator.renameFile(oldName, editingSD.docName);
					}

					DropboxCommunicator.saveSingleFile(editingSD.docName, doc, 0);

					editingSD.isModified = false;

				}

				if(editingSD.lockingStatus == LOCKED_BY_ME){

					DropboxCommunicator.unlockFile(editingSD.docName);
				}
			}
		}
	}

	public String toString ()
	{
		String returnString = "BeforeList:\n";
		for(int i = 0; i < beforeSDList.size(); i++){
			returnString += beforeSDList.get(i) + "\n";
		}
		returnString += "Current working list:\n";
		for (int i = 0; i < activeSDList.size(); i++)
		{
			returnString += activeSDList.get(i).toString();
		}

		return returnString;
	}

	public SmallDocument geteditingSD(){

		return editingSD;
	}
	
	
	// Author@U097023U 
	protected void rememberCaretPosition ()
	{
		//lastCaretPosition = editor.getEditor ().getCaretPosition ();
		DefaultCaret caret = (DefaultCaret) editor.getEditor ().getCaret ();
		caret.setUpdatePolicy (DefaultCaret.NEVER_UPDATE);
	}
	
	// Author@U097023U 
	protected void resetCaretToLastPosition ()
	{
		//editor.getEditor ().setCaretPosition (lastCaretPosition);
		DefaultCaret caret = (DefaultCaret) editor.getEditor ().getCaret ();
		caret.setUpdatePolicy (DefaultCaret.ALWAYS_UPDATE);
	}
	


	/**
	 * the main function to update the editor UI
	 * 
	 * updateType = 0: a list of files have been deleted by others
	 * updateType = 1: a list of files have been modified by others
	 * updateType = 2: add a new SD at the bottom
	 * updateType = 3: add a new SD in the front
	 * 
	 * @param docs
	 * @param filenames
	 * @param updateType
	 */
	public synchronized void updateNotification(ArrayList<Document> docs, ArrayList<String> filenames, int updateType){

		SmallDocument sd;

		synchronized(editor){
			synchronized(activeSDList){

				// Update a list of files to be deleted from local
				if (updateType == 0)
				{
					for (int i = 0; i < filenames.size (); i++){

						String findFile = SDAction.getUnchangedPartInName(filenames.get(i));

						// check whether the file is updated by me
						if(editingSD != null && findFile.equals (SDAction.getUnchangedPartInName (editingSD.docName)))
							continue;

						sd = findRequestedSD (findFile);

						// If the file to delete is inside the EditorUI
						if (sd != null)
						{
							Element root = editor.getEditor().getDocument().getDefaultRootElement();
							Element startPara = root.getElement(sd.firstParaIndex);
							Element endPara = root.getElement(sd.firstParaIndex + sd.numberOfParas-1);

							int firstChar = startPara.getStartOffset();
							int lastChar = endPara.getEndOffset();

							editor.removeUndoableListener();
							editor.removeMyDocumentListener();
	
							try 
							{
								rememberCaretPosition ();
								if (sd == activeSDList.get(activeSDList.size() - 1))
								{
									
									editor.getEditor().getDocument().remove (firstChar - 1, lastChar - firstChar);
								}
								else
								{
									
									editor.getEditor().getDocument().remove (firstChar, lastChar - firstChar);
								}
								resetCaretToLastPosition ();
									
							} catch (BadLocationException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}

							editor.addUndoableListener();
							editor.addMyDocumentListener();

							deleteSD(sd);
						}
						else if(newFileSDPosition (findFile) == 0)
						{
							removeFromBeforeList (filenames.get(i));
						}
					}			
				}				

				// Update a list of modified or newly added in files
				else if (updateType == 1){

					for (int i = 0; i < filenames.size(); i++){

						String findFile = SDAction.getUnchangedPartInName(filenames.get(i));

						// check whether the file is updated by me
						if(editingSD != null && findFile.equals(SDAction.getUnchangedPartInName(editingSD.docName)))
							continue;

						sd = findRequestedSD(findFile);

						Element root = editor.getEditor().getDocument().getDefaultRootElement();
						Element fileroot = docs.get(i).getDefaultRootElement();
						int numParas = fileroot.getElementCount();

						// when the modified file is inside the EditorUI
						if (sd != null)
						{
							rememberCaretPosition ();
							Element startpa = root.getElement(sd.firstParaIndex);
							Element endpa = root.getElement(sd.firstParaIndex + sd.numberOfParas-1);

							int firstChar = startpa.getStartOffset();
							int lastChar = endpa.getEndOffset() - 1;

							editor.removeUndoableListener();
							editor.removeMyDocumentListener();

							try {
								editor.getEditor().getDocument().remove(firstChar, lastChar - firstChar);
							} catch (BadLocationException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}
							//a new line is left
							
							int s = startpa.getStartOffset();
							int length = 0;
							// try to update the file
							for (int i1 = 0; i1 < docs.get(i).getDefaultRootElement().getElementCount(); i1++)
							{
								Element p = docs.get(i).getDefaultRootElement().getElement(i1);
								int numRuns = p.getElementCount();
								for (int j = 0; j < numRuns; j++)
								{
									Element content = p.getElement(j);
									int rangeStart = content.getStartOffset();
									int rangeEnd = content.getEndOffset();
									length = rangeEnd - rangeStart;
									AttributeSet as = content.getAttributes();

									try 
									{	
										editor.getEditor().getDocument().insertString(s, docs.get(i).getText(rangeStart, rangeEnd - rangeStart), as);
										s += length;
									} 
									catch (BadLocationException e) 
									{
										e.printStackTrace();
									}
								}

							}

							/**
							 * remove the left line
							 */
							try {
								editor.getEditor().getDocument().remove(s-1, 1);
							} catch (BadLocationException e) {
								e.printStackTrace();
							}

							editor.addUndoableListener();
							editor.addMyDocumentListener();

							sd.docName = filenames.get(i);	
							sd.lockingStatus = SDAction.getLockingStatus(sd.docName);
							setParagraphBackground(sd, SDAction.getLockingStatus(sd.docName));

							sd.numberOfParas = numParas;

							for (int j = sd.sdIndex + 1; j < activeSDList.size(); j++)
							{

								activeSDList.get(j).firstParaIndex = activeSDList.get(j - 1).firstParaIndex + activeSDList.get(j - 1).numberOfParas;

							}
							resetCaretToLastPosition ();
						}

						// when the modified file is Not inside the EditorUI
						else{
							int nextIndex = newFileSDPosition (findFile);

							// it's a new file within the editing range
							if(nextIndex > 0 && nextIndex < activeSDList.size()){

								SmallDocument sdNext = activeSDList.get(nextIndex);

								Element start = root.getElement(sdNext.firstParaIndex);
								int s = start.getStartOffset();					
								boolean colorChanged = false;
						
								if(sdNext.lockingStatus != 0){
									setParagraphBackground (sdNext, 0);
									colorChanged = true;
								}
								
								editor.removeUndoableListener();
								editor.removeMyDocumentListener();

								for (int i1 = 0; i1 < numParas; i1++)
								{
									Element p = fileroot.getElement(i1);
									int numRuns = p.getElementCount();
									int length = 0;
									for (int j = 0; j < numRuns; j++)
									{
										Element content = p.getElement(j);
										int rangeStart = content.getStartOffset();
										int rangeEnd = content.getEndOffset();
										AttributeSet as = content.getAttributes();
										length = rangeEnd - rangeStart;
										try 
										{	
											editor.getEditor().getDocument().insertString(s, docs.get(i).getText(rangeStart, rangeEnd - rangeStart), as);
											s += length;
										} 
										catch (BadLocationException e1) 
										{
											e1.printStackTrace();
										}
									}

								}							

								editor.addUndoableListener();
								editor.addMyDocumentListener();
								
								sd = new SmallDocument();
								sd.firstParaIndex = sdNext.firstParaIndex;
								sd.numberOfParas = numParas;
								sd.docName = filenames.get(i);
								sd.lockingStatus = SDAction.getLockingStatus(sd.docName);
								sd.sdIndex = sdNext.sdIndex;

								activeSDList.add(sd.sdIndex, sd);

								lockList.add(sd.sdIndex, new Object());

								for (int j = sd.sdIndex + 1; j < activeSDList.size(); j++)
								{
									activeSDList.get(j).sdIndex = j;
									activeSDList.get(j).firstParaIndex = activeSDList.get(j).firstParaIndex + sd.numberOfParas;

								}

								if(sd.lockingStatus != 0)
									setParagraphBackground (sd, sd.lockingStatus);
								
								if(colorChanged){
									sdNext = activeSDList.get(sd.sdIndex + 1);
									setParagraphBackground (sdNext, sdNext.lockingStatus);
									colorChanged = false;
								}
								
								/**
								 * next, check whether the number of SDs has exceeds the max limit
								 * if exceeds, then delete the last SD
								 * if the last SD is locked by the user, 
								 * then delete the first SD
								 */
								

							}
							else
								// a file before the editing range, need to update the beforeList
								if(nextIndex == 0){

									addToBeforeList(filenames.get(i));
								}
						}

					}

				}

				// respond to Next actions
				else if(updateType == 2){

					for(int i = 0; i < filenames.size(); i++)
					{

						Element fileroot = docs.get(i).getDefaultRootElement();
						int numParas = fileroot.getElementCount();

						int s = editor.getEditor().getDocument().getLength();					

						boolean colorChanged = false;
						if(activeSDList.get(activeSDList.size() - 1).lockingStatus != 0){
							setParagraphBackground(activeSDList.get(activeSDList.size() - 1), 0);
							colorChanged = true;
						}
						
						editor.removeUndoableListener();
						editor.removeMyDocumentListener();
						
						
						
						if(s > 0){
							try {
								editor.getEditor().getDocument().insertString(s, "\n", null);
								s++;
							} catch (BadLocationException e) {
								e.printStackTrace();
							}
						}

						for (int i1 = 0; i1 < numParas; i1++)
						{
							Element p = fileroot.getElement(i1);
							int numRuns = p.getElementCount();
							int length = 0;
							for (int j = 0; j < numRuns; j++)
							{
								Element content = p.getElement(j);
								int rangeStart = content.getStartOffset();
								int rangeEnd = content.getEndOffset();
								AttributeSet as = content.getAttributes();
								length = rangeEnd - rangeStart;
								try 
								{	
									editor.getEditor().getDocument().insertString(s, docs.get(i).getText(rangeStart, rangeEnd - rangeStart), as);
									s += length;
								} 
								catch (BadLocationException e1) 
								{
									e1.printStackTrace();
								}
							}

						}

						try {
							editor.getEditor().getDocument().remove(editor.getEditor().getDocument().getLength()-1, 1);
						} catch (BadLocationException e) {
							e.printStackTrace();
						}

						editor.getEditor().setCaretPosition(editor.getEditor().getDocument().getLength() - docs.get(i).getLength());

						sd = new SmallDocument(filenames.get(i));
						//sd.sdNo = SDAction.getSDNoFromFileName(filenames.get(i));
						sd.sdIndex = activeSDList.size();

						//removeHighlight (activeSDList.get(activeSDList.size () - 1));
						//recoverHighlight (activeSDList.get(activeSDList.size () - 1));

						/*if(SDAction.isFileLocked(sd.docName))
						{	
							setParagraphBackground (sd, LOCKED_BY_OTHER);
							sd.lockingStatus = LOCKED_BY_OTHER;
						}
*/
						sd.lockingStatus = SDAction.getLockingStatus(sd.docName);
						
						int paracount = 0;
						for(int j = 0; j < activeSDList.size(); j++)
							paracount += activeSDList.get(j).numberOfParas;
						sd.firstParaIndex = paracount;

						sd.numberOfParas = numParas;
						activeSDList.add (sd);
						lockList.add(new Object());

						// if the document showed to the user is too long, remove the first sd
						/*if(activeSDList.size() > MAX_SD_CAN_SHOW && editingSD != activeSDList.get(0)){
							int start = editor.getEditor().getDocument().getDefaultRootElement().getElement(activeSDList.get(0).firstParaIndex).getStartOffset();
							int end = editor.getEditor().getDocument().getDefaultRootElement()
							.getElement(activeSDList.get(0).firstParaIndex + activeSDList.get(0).numberOfParas - 1).getEndOffset();

							try {
								editor.getEditor().getDocument().remove(start, end - start);
							} catch (BadLocationException e) {
								e.printStackTrace();
							}

							SmallDocument sd0 = activeSDList.get(0);

							activeSDList.remove(0);
							lockList.remove(0);

							for(int j = 0; j < activeSDList.size(); j++){
								activeSDList.get(j).sdIndex = j;
								activeSDList.get(j).firstParaIndex -= sd0.numberOfParas;
							}

							addToBeforeList(sd0.docName);
						}*/

						editor.addUndoableListener();
						editor.addMyDocumentListener();
						
						if(sd.lockingStatus != 0)
							setParagraphBackground(sd, sd.lockingStatus);
						
						if(colorChanged){
							sd = activeSDList.get(activeSDList.size()-2);
							setParagraphBackground(sd, sd.lockingStatus);
							colorChanged = false;
						}
						
						
					}

				}
				// respond to Previous actions
				else if(updateType == 3){

					for(int i = 0; i < filenames.size(); i++)
					{

						Element fileroot = docs.get(i).getDefaultRootElement();
						int numParas = fileroot.getElementCount();

						int s = 0;					

						boolean colorChanged = false;
						
						if(activeSDList.get(0).lockingStatus != 0){
							setParagraphBackground(activeSDList.get(0), 0);
							colorChanged = true;
						}

						editor.removeUndoableListener();
						editor.removeMyDocumentListener();

						for (int i1 = 0; i1 < numParas; i1++)
						{
							Element p = fileroot.getElement(i1);
							int numRuns = p.getElementCount();
							int length = 0;
							for (int j = 0; j < numRuns; j++)
							{
								Element content = p.getElement(j);
								int rangeStart = content.getStartOffset();
								int rangeEnd = content.getEndOffset();
								AttributeSet as = content.getAttributes();
								length = rangeEnd - rangeStart;
								try 
								{	
									editor.getEditor().getDocument().insertString(s, docs.get(i).getText(rangeStart, rangeEnd - rangeStart), as);
									s += length;
								} 
								catch (BadLocationException e1) 
								{
									e1.printStackTrace();
								}
							}

						}

						editor.getEditor().setCaretPosition(docs.get(i).getLength());

						sd = new SmallDocument(filenames.get(i));
						//sd.sdNo = SDAction.getSDNoFromFileName(filenames.get(i));
						sd.sdIndex = 0;
						sd.firstParaIndex = 0;
						sd.numberOfParas = numParas;
						sd.lockingStatus = SDAction.getLockingStatus(sd.docName);
						
						//removeHighlight (activeSDList.get(0));
						//recoverHighlight (activeSDList.get(0));

						activeSDList.add (0, sd);
						lockList.add(0, new Object());

						for(int j = 1; j < activeSDList.size(); j++){
							activeSDList.get(j).sdIndex = j;
							activeSDList.get(j).firstParaIndex += numParas;
						}
						
						

						removeFromBeforeList(sd.docName);

						// if the document showed to the user is too long, remove the last sd
						/*if(activeSDList.size() > MAX_SD_CAN_SHOW && editingSD != activeSDList.get(activeSDList.size()-1)){
							int start = editor.getEditor().getDocument().getDefaultRootElement().
							getElement(activeSDList.get(activeSDList.size()-1).firstParaIndex).getStartOffset();
							int end = editor.getEditor().getDocument().getDefaultRootElement().
							getElement(activeSDList.get(activeSDList.size()-1).firstParaIndex + activeSDList.get(activeSDList.size()-1).numberOfParas - 1).getEndOffset();

							System.out.println(start + ", " + end);
							try {
								editor.getEditor().getDocument().remove(start-1, end - start);
							} catch (BadLocationException e) {
								e.printStackTrace();
							}

							activeSDList.remove(activeSDList.size()-1);

							lockList.remove(activeSDList.size() - 1);
						}*/

						editor.addUndoableListener();
						editor.addMyDocumentListener();
						
						if(sd.lockingStatus != 0)
						{	
							setParagraphBackground (sd, LOCKED_BY_OTHER);

						}

						if(colorChanged){
							setParagraphBackground (activeSDList.get(1), activeSDList.get(1).lockingStatus);
							colorChanged = false;
						}
					}
				}

			}
		}

		printActiveSDList("UpdateNotification");

	}


	private void addToBeforeList(String docName) {

		int i;
		for(i = 0; i < beforeSDList.size(); i++)
			if(SDAction.getSDNoFromFileName(beforeSDList.get(i)) >= SDAction.getSDNoFromFileName(docName))
				break;

		if(i < beforeSDList.size() && SDAction.getSDNoFromFileName(beforeSDList.get(i)) == SDAction.getSDNoFromFileName(docName))
			beforeSDList.set(i, docName);
		else
			beforeSDList.add(i, docName);
	}

	private void removeFromBeforeList(String docName){

		int i;
		for(i = 0; i < beforeSDList.size(); i++)
			if(SDAction.getSDNoFromFileName(beforeSDList.get(i)) == SDAction.getSDNoFromFileName(docName))
				break;

		if(i < beforeSDList.size())
			beforeSDList.remove(i);
		else
			System.err.println("Error! Cannot find the doc name to delete!");
	}

	public int countSDLinesBefore(){

		int lines = 0;

		for(int i = 0; i < beforeSDList.size(); i++)
			lines += SDAction.getLineNoFromFileName(beforeSDList.get(i));

		return lines;
	}

	public  SmallDocument findRequestedSD(String unchangedPartInName)
	{
		synchronized(activeSDList){

			for (int i = 0; i < activeSDList.size(); i++){

				String localFile = SDAction.getUnchangedPartInName(activeSDList.get(i).docName);

				if (localFile.equals(unchangedPartInName))
					return activeSDList.get(i);

			}
		}

		return null;


	}

	public int newFileSDPosition(String fileName){

		float newFile = SDAction.getSDNoFromFileName(fileName);

		synchronized(activeSDList){

			for (int i = 0; i < activeSDList.size(); i++){

				if (SDAction.getSDNoFromFileName(activeSDList.get(i).docName) > newFile)

					return i;	
			}
		}

		return activeSDList.size();

	}	

	// Author@U097023U
	public void startTimer(){
		m_t = new javax.swing.Timer (60000, new ClockTickAction());
		m_t.start();  
	}
	// Author@U097023U
	public void stopTimer()
	{
		if (m_t != null)
		{
			m_t.stop();
			m_t = null;
		}
	}

	// Author@U097023U
	private class ClockTickAction implements ActionListener {
		public void actionPerformed(ActionEvent e) 
		{
			if(editingSD == null)
				return;

			synchronized(editingSD){

				if(editingSD.lockingStatus == LOCKED_BY_ME){

					System.out.println("Auto-unlock thread started for file: " + editingSD.docName); 

					new FileUnlocker(editingSD).start();
					editingSD.lockingStatus = LOCKED_BY_NONE;
					setParagraphBackground(editingSD, 0);

					editingSD = null;
				}
			}

		}

	}
	// Author@U097023U
	public class MyHighlighter extends DefaultHighlighter
	{
		private JTextComponent component;

		/**
		 * @see javax.swing.text.DefaultHighlighter#install(javax.swing.text.JTextComponent)
		 */
		@Override
		public final void install(final JTextComponent c)
		{
			super.install(c);
			this.component = c;
		}

		/**
		 * @see javax.swing.text.DefaultHighlighter#deinstall(javax.swing.text.JTextComponent)
		 */
		@Override
		public final void deinstall(final JTextComponent c)
		{
			super.deinstall(c);
			this.component = null;
			
		}
		
		@Override
		public final void removeHighlight (Object tag)
		{
			super.removeHighlight (tag);
		}

		/**
		 * Same algo, except width is not modified with the insets.
		 * 
		 * @see javax.swing.text.DefaultHighlighter#paint(java.awt.Graphics)
		 */
		@Override
		public final void paint(final Graphics g)
		{
			
			final Highlighter.Highlight[] highlights = getHighlights();
			final int len = highlights.length;
			
			/*for (int i = 0; i < len; i++)
			{
				Highlighter.Highlight info = highlights[i];
				if (info.getClass().getName().indexOf("LayeredHighlightInfo") > -1)
				{*/
					// Avoid allocating unless we need it.
					final Rectangle a = this.component.getBounds();
					final Insets insets = this.component.getInsets();
					a.x = insets.left;
					a.y = insets.top;
					//a.width -= insets.left + insets.right + 100;
					a.height -= insets.top + insets.bottom;
					for (int i = 0; i < len; i++)
					{
						Highlighter.Highlight info = highlights[i];
						
						if (info.getClass().getName().indexOf("LayeredHighlightInfo") > -1)
						{
							
							final Highlighter.HighlightPainter p = info.getPainter();
							
							p.paint (g, info.getStartOffset(), info.getEndOffset(), a, this.component);
						}
					}
				//}
			//}
		}
	}

	/**
	 * the thread run as soon as the SmartManager is started
	 * check every 10s on the current editingSD,
	 * if it has been modified, save the changes
	 * 
	 * @author g0900398
	 *
	 */
	public class AutoFileSaver extends Thread{

		private int AUTO_SAVING_INTERVAL = 10;
		
		Object lock;

		public AutoFileSaver(){

			lock = new Object();
		}

		public AutoFileSaver(int interval){
			this.AUTO_SAVING_INTERVAL = interval;
			
			lock = new Object();
		}

		public void run(){

			while(true){
				try {
					Thread.sleep(AUTO_SAVING_INTERVAL * 1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				SmallDocument thisSD = editingSD;
				
				if(thisSD == null)
					continue;
				
				//synchronized(editor){
					synchronized(thisSD){

						if(thisSD.isModified && thisSD.lockingStatus == LOCKED_BY_ME)
						{	

							/**
							 * the sub-file currently being locked by the user 
							 * has been modified since last saving action, so 
							 * try to save this sub-file, and check whether this
							 * sub-file has to be divided or merged
							 */

							System.out.println("Auto-saving started for file: " + thisSD.docName);

							int sdLines = SDAction.getSDLines(editor, thisSD);

							System.out.println("Current lines: " + sdLines);

							/**
							 * if the sub-file is larger than max line limit,
							 * divide this sub-file and update the activeSDList
							 */
							if (sdLines > MAX_LINE_NUM && thisSD.numberOfParas > 1){
								
								new FileDivider(thisSD, lock).start();
								
							}

							/**
							 * if the sub-file is smaller than the min line condition,
							 * this sub-file may have to be merged with another sub-file
							 */
							else if(sdLines < MIN_LINE_NUM){

								new FileMerger(thisSD, lock).start();
								
							}

							/**
							 * the size of this sub-file is within the range,
							 * so just save back this sub-file
							 */
							else{
								new NormalFileSaver(thisSD, lock).start();
							}

						}

						thisSD.isModified = false;
					}
				//}

			}
		}
	}

	/**
	 * a separate thread which is started to divide the (large) editingSD
	 * into two smaller SDs
	 * 
	 * @author g0900398
	 *
	 */
	public class FileDivider extends Thread{

		SmallDocument currentSD;
		Object isSaving;
		
		public FileDivider(SmallDocument sd, Object lock){

			this.currentSD = sd;
			this.isSaving = lock;
		}

		public void run(){

			System.out.println("Start dividing the sub-file, please wait a while...");
			
			synchronized(isSaving){
				
				SmallDocument sd;
				SmallDocument sd2;
				String oldName;

				Document doc;
				Document doc2;

				synchronized(editor){
					synchronized(currentSD){
						synchronized(activeSDList){
						
							float nextSDNo;

							sd = currentSD;
							
							removeHighlight(sd);
							
							sd2 = getNextSD (currentSD);
							// The modified small document is the last one.
							if (sd2 == null)
							{
								String nextFileNameNotLoaded = getNextSDNotLoadYet (null, sd);
								// if the nextSD exists in the directory, but not load yet
								if (!nextFileNameNotLoaded.equals(""))
									nextSDNo = SDAction.getSDNoFromFileName(nextFileNameNotLoaded);

								// Otherwise the currentSD is the last SD in the whole document
								else
									nextSDNo = SDAction.getSDNoFromFileName(sd.docName) + 2.0f;
							}
							// The nextSD is already loaded into Editor.
							else
								nextSDNo = SDAction.getSDNoFromFileName(sd2.docName);

							float newSDNo = (SDAction.getSDNoFromFileName(sd.docName) + nextSDNo) / 2.0f;

							oldName = sd.docName;

							sd2 = new SmallDocument(SDAction.createSDName(newSDNo));

							//update the info of sd and sd2 accordingly
							new Divide(editor, sd, sd2).process();

							recoverHighlight(sd);
							recoverHighlight(sd2);
							//update the activeSDList
							insertSD(sd2, getSDIndex(sd)+1);

							/**
							 * next, reflect the changes in the editor
							 */
							int changedIndex = editor.getEditor().getDocument().getDefaultRootElement().getElementIndex
							(editor.getEditor().getCaretPosition());

							editingSD = findSD(changedIndex);

							if(editingSD == sd){
								sd2.docName = SDAction.renameFromLockToUnlock(sd2.docName);
								sd2.lockingStatus = LOCKED_BY_NONE;

								setParagraphBackground(sd, LOCKED_BY_ME);
								setParagraphBackground(sd2, LOCKED_BY_NONE);
								//editor.validate();
							}
							else if(editingSD == sd2){
								sd.docName = SDAction.renameFromLockToUnlock(sd.docName);
								sd.lockingStatus = LOCKED_BY_NONE;

								setParagraphBackground(sd2, LOCKED_BY_ME);
								setParagraphBackground(sd, LOCKED_BY_NONE);
								//editor.validate();
							}
							else{
								sd.docName = SDAction.renameFromLockToUnlock(sd.docName);
								sd.lockingStatus = LOCKED_BY_NONE;
								sd2.docName = SDAction.renameFromLockToUnlock(sd2.docName);
								sd2.lockingStatus = LOCKED_BY_NONE;

								setParagraphBackground(sd, LOCKED_BY_NONE);
								setParagraphBackground(sd2, LOCKED_BY_NONE);

								//editingSD = null;
							}

							/**
							 * prepare the documents to be saved
							 */
							doc = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
									sd.firstParaIndex, sd.firstParaIndex + sd.numberOfParas);

							doc2 = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
									sd2.firstParaIndex, sd2.firstParaIndex + sd2.numberOfParas);


						}
					}
				}

				/**
				 * finally, save the changes
				 */
				synchronized(lockList.get(getSDIndex(sd))){

					DropboxCommunicator.renameFile(oldName, sd.docName);
					//u0909048: ops is 0, modification of subfile
					DropboxCommunicator.saveSingleFile(sd.docName , doc, 0);
				}

				synchronized(lockList.get(getSDIndex(sd2))){
					//u0909048: ops is 1, addition of new subfile
					DropboxCommunicator.saveSingleFile(sd2.docName , doc2, 1);
				}
				
			}
			
			System.out.println("File dividing completed!!!");
		}

	}

	/**
	 * a separate thread which is started to merge the editingSD
	 * with its previous or next SD, if satisfy certain conditions
	 * 
	 * @author u097023u, g0900398
	 *
	 */
	public class FileMerger extends Thread{

		SmallDocument currentSD;
		Object isSaving;
		
		public FileMerger(SmallDocument sd, Object lock){

			this.currentSD = sd;
			this.isSaving = lock;
		}

		public void run(){

			System.out.println("Start merging the sub-file, please wait a while...");
			
			synchronized(isSaving){
				
				SmallDocument topSD;
				SmallDocument bottomSD;
				String oldname;
				Document doc;

				synchronized(editor){
					synchronized(currentSD){
						synchronized(activeSDList){

							SmallDocument curSD = currentSD;
							SmallDocument sdPrev = getPrevSD (currentSD);
							SmallDocument sdNext = getNextSD (currentSD);

							int totalLines;
							//String nextUnloadedSDFileName;
							/**
							 * first check whether this SD can be merged with its previous SD
							 */
							if(sdPrev != null && sdPrev.lockingStatus == 0 &&
									(totalLines = SDAction.getSDLines(editor, sdPrev) + SDAction.getSDLines(editor, curSD)) < MAX_LINE_NUM){

								boolean res;
								res = DropboxCommunicator.lockFile(sdPrev.docName);

								if(res)
								{
									removeHighlight(curSD);
									
									sdPrev.docName = SDAction.renameFromUnlockToLock(sdPrev.docName);
									sdPrev.lockingStatus = LOCKED_BY_ME;
									oldname = sdPrev.docName;
									new Combine(editor, sdPrev, currentSD, totalLines).process();

									deleteSD (curSD.sdIndex);
									editingSD = sdPrev;

									setParagraphBackground (sdPrev, LOCKED_BY_ME);
										

									topSD = sdPrev;
									bottomSD = curSD;
									doc = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
											topSD.firstParaIndex , topSD.firstParaIndex + topSD.numberOfParas);
								}
								else{
									new NormalFileSaver(curSD, isSaving).start();
									return;
								}
							}

							/**
							 * next, check whether this SD can be merged with its next SD
							 */
							else if(sdNext != null && sdNext.lockingStatus == 0 &&
									(totalLines = SDAction.getSDLines(editor, sdNext) + SDAction.getSDLines(editor, curSD)) < MAX_LINE_NUM){

								boolean res;
								res = DropboxCommunicator.lockFile(sdNext.docName);

								if(res){

									removeHighlight(curSD);
									sdNext.docName = SDAction.renameFromUnlockToLock(sdNext.docName);
									sdNext.lockingStatus = LOCKED_BY_ME;

									oldname = curSD.docName;
									new Combine (editor, curSD, sdNext, totalLines).process();


									deleteSD (sdNext.sdIndex);

									editingSD = curSD;

									setParagraphBackground (curSD, LOCKED_BY_ME);

									topSD = curSD;
									bottomSD = sdNext;
									doc = SDAction.retrieveSingleDocument(editor.getEditor().getDocument(),
											topSD.firstParaIndex , topSD.firstParaIndex + topSD.numberOfParas);
								}
								else{
									new NormalFileSaver(curSD, isSaving).start();
									return;
								}
							}

							/**
							 * finally, check whether this SD can be merged with a SD which has not been loaded yet
							 * (this function currently not supported)
							 */
							/*else if(!(nextUnloadedSDFileName = getNextSDNotLoadYet (null, currentSD)).equals("") &&
										SDAction.getLineNoFromFileName(nextUnloadedSDFileName) + SDAction.getSDLines(editor, currentSD)
										< MAX_LINE_NUM && !SDAction.isFileLocked (nextUnloadedSDFileName)){

							 }*/
							else{
								new NormalFileSaver(curSD, isSaving).start();
								return;
							}
						}
					}
				}

				synchronized(lockList.get(getSDIndex(topSD)))
				{
					String backUpFolder = DropboxCommunicator.backUpPath + "/Combine_" + SDAction.renameFromLockToUnlock (oldname);
					DropboxCommunicator.createNewFolder (backUpFolder);
					DropboxCommunicator.copyFile (backUpFolder, SDAction.renameFromLockToUnlock (oldname), oldname);
					DropboxCommunicator.copyFile (backUpFolder, SDAction.renameFromLockToUnlock (bottomSD.docName), bottomSD.docName);
					
					// Test here ************
					DropboxCommunicator.renameFile (oldname, topSD.docName);
					DropboxCommunicator.saveSingleFile (topSD.docName, doc,2);
					//DropboxCommunicator.deleteSingleFile (bottomSD.docName);
					
					//DropboxCommunicator.deleteSingleFile (backUpFolder);
				}

				
			}
			
			System.out.println("File merging completed!!!");
		}
	}
	
	
	// @Author: U097023U
	class Undo extends Thread
	{
		private int numUndoSteps = 1;
		
		public Undo (int numUndoSteps)
		{
			this.numUndoSteps = numUndoSteps;
		}
		
		public void run ()
		{
			JOptionPane.showMessageDialog (editor.getFrame (),
					"You are not allowed to delete other than the currently editting content.",
					"Deletion Failed",
					JOptionPane.ERROR_MESSAGE);
			redoDeleteAction = true;
			editor.undoNSteps (numUndoSteps);
			
		}
	}
}
